FFHS DAS Data Science
Semesterarbeit FS20
Iwan Imsand
Statistische Datenanalyse, StatDa
Nach der ersten Präsenz
In diesem Kapitel wird eine deskriptive Analyse für den gewählten Datensatz durchgeführt.
TODO
Folgende Quellen wurden benutzt:
Die Datenbeschaffung, Bereinigung und Zusammenführung wurde in den Notebooks 0_*.ipynb durchgeführt.
Als Grundlage für alle nachfolgenden Analysen, dienen die folgenden Daten:
| Dateiname | Beschreibung |
|---|---|
samples_5000_201910-citibike-tripweather-data.parquet |
Enthält 5000 zufällig gewählte Stichproben aus dem Monat Oktober des Jahres 2019. |
summary-daily-subscribers_only-citibike-tripweather.parquet |
Enthält eine aggregierte Zusammenfassung pro Tag über alle Jahre. Es wurden nur Jahresmitglieder berücksichtigt! |
summary-daily-subscribers_only-gender_grouped-citibike-tripweather.parquet |
Enthält eine nach Geschlecht gruppierte und aggregierte Zusammenfassung pro Tag über alle Jahre. Es wurden nur Jahresmitglieder berücksichtigt! |
Alle Dateien befinden sich im Pfad ./../data/citibike/tripdata/.
import os
import pandas as pd
import numpy as np
import math
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import matplotlib.dates as mdates
from matplotlib.lines import Line2D
import seaborn as sns
from datetime import datetime
from dateutil import tz
import sidetable
from plotnine import *
%matplotlib inline
# Needed for correct time plots
matplotlib.rcParams['timezone'] = 'US/Eastern'
pd.options.display.float_format = '{:.5f}'.format
path = './../data/citibike/tripdata'
def show_hist(data, xlabel, ylabel, title, nbins=0, log=False, figsize=(15,2)):
'''
Shows a histogram of data with som other informations.
The bin size is calculated with math.sqrt(data.count()).
'''
fig = plt.figure(figsize=figsize)
if nbins == 0:
nbins = int(math.sqrt(data.count()))
ax = plt.subplot()
n, bins, patches = ax.hist(data, bins=nbins, log=log)
ax.set_xlabel(xlabel)
if log:
ax.set_ylabel(ylabel + '(log)')
else:
ax.set_ylabel(ylabel + '(lin)')
ax.set_title(title)
plt.show()
print('Anzahl Klassen: {}\nBreite einer Klasse: {:.5f}\nSkew: {:.5f}\nKurt: {:.5f}'.format(
len(n),
np.diff(bins)[0],
data.skew(),
data.kurt())
)
return n, bins, patches
samples_5000_201910-citibike-tripweather-data.parquet¶df_oct2019 = pd.read_parquet(os.path.join(path, 'samples_5000_201910-citibike-tripweather-data.parquet'))
df_oct2019.head().T
Die Merkmale die in der Datei samples_5000_201910-citibike-tripweather-data.parquet enthalten sind, werden in folgender Tabelle beschrieben.
| Statistische Einheit | Merkmal | Merkmalsausprägung / Beispiel | Skalenniveau | Kontinuität | Beschreibung |
|---|---|---|---|---|---|
| Trip | dt_utc | 2019-10-01 400:00:05+00:00 | Intervall | stetig | Zeitpunkt an welchem der Trip gestartet wurde in 'Koordinierter Weltzeit' (UTC) |
| Trip | Trip Duration | 100 Sekunden | Verhältnis | stetig | Dauer des Trips in Sekunden |
| Trip | Start Time | 2019-01-01 00:01:05-04:00 | Intervall | stetig | Zeitpunkt an welchem der Trip gestartet wurde mit Zeitzone US/Eastern |
| Trip | Stop Time | 2019-01-01 00:07:07-04:00 | Intervall | stetig | Zeitpunkt an welchem der Trip beendet wurde mit Zeitzone US/Eastern |
| Trip | Linear Distance | 1.55503 | Verhältnis | stetig | Distanz der Luftlinie zwischen Start und Stop Station in km (Berechnet mit haversine) |
| Station | Start Station ID | 3160 | Nominal | diskret | Eindeutige Identifikation der Station an welcher der Trip gestartet wurde |
| Station | Start Station Name | Central Park West & W 76 St | Nominal | diskret | Name der Startstation |
| Station | Start Station Latitude | 40.778968 | Intervall | stetig | Breitengrad der Startstation |
| Station | Start Station Longitude | -73.973747 | Intervall | stetig | Längengrad der Startstation |
| Station | End Station ID | 3283 | Nominal | diskret | Eindeutige Identifikation der der Station an welcher der Trip beendet wurde |
| Station | End Station Name | W 89 St & Columbus Ave | Nominal | diskret | Name der Endstation |
| Station | End Station Latitude | 40.788221 | Intervall | stetig | Breitengrad der Endstation |
| Station | End Station Longitude | -74.00597 | Intervall | stetig | Längengrad der Endstation |
| Bike | Bike ID | 15839 | Nominal | diskret | Eindeutige Identifikation des Bikes |
| User | User Type | Subscriber | Nominal | diskret | Benutzertyp: Customers=24-hour pass oder 3-day pass; Subscribers=Annual Member |
| User | Birth Year | 1971 | Intervall | diskret | Geburtsjahr des Benutzers |
| User | Gender | 1 | Nominal | diskret | Geschlecht des Benutzers: 0=unknown; 1=male; 2=female |
| User | Age 2020 | 28 | Verhältnis | diskret | Alter des Benutzers im Jahr 2020 in Jahren |
| Temperature | temp | 17.52 | Intervall | stetig | Temperatur in Grad Celsius |
| Air | pressure | 1023 | Intervall | stetig | Luftdruck (auf Meereshöhe), hPa |
| Air | humidity | 75 | Intervall | stetig | Luftfeuchtigkeit in % |
| Wind | wind_speed | 5.27 | Verhältnis | stetig | Windgeschwindigkeit in m/s |
| Rainfall | rain_1h | 0.25 | Verhältnis | stetig | Regenmenge der letzten Stunde in mm |
| Snowfall | snow_1h | 0.00 | Verhältnis | stetig | Schneemenge der letzten Stunde in mm |
| Clouds | clouds_all | 100 | Intervall | stetig | Bewölkung in % |
| Weather | weather_id | 500 | Nominal | diskret | Code für die Wetterbedingung (Aufgelistet unter https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2) |
| Weather | weather_main | Rain | Nominal | diskret | Gruppe der Wetterbedingung (z.B. Rain,Clear,Clouds, für weitere: https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2) |
| Weather | weather_description | light rain | Nominal | diskret | Beschreibung der Wetterbedingung (z.B. light rain, sky is clear, few clouds, für weitere https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2) |
Damit eine Interpretation einiger Merkmale in der Analyse einfacher wird, werden zusätzliche Spalten in anderen Einheiten hinzugefügt.
df_oct2019.insert(loc=2, column='Trip Duration (min)', value=df_oct2019['Trip Duration'] / 60)
df_oct2019.insert(loc=3, column='Trip Duration (h)', value=df_oct2019['Trip Duration'] / 60 / 60)
df_oct2019.insert(loc=4, column='Trip Duration (d)', value=df_oct2019['Trip Duration'] / 60 / 60 / 24)
Folgende Spalten wurden ergänzt:
[col for col in df_oct2019.columns if col.endswith(')') ]
Untersuchungen zur Anzahl der Trips im Oktober 2019.
df_oct2019_idx = df_oct2019.set_index(pd.DatetimeIndex(data=df_oct2019['Start Time'].dt.date, name='Date', tz=tz.gettz('UTC')))
df_oct2019_grouped = df_oct2019_idx.groupby(by=['Date'])[['Trip Duration']].agg('count').rename({'Trip Duration': 'Trip count'}, axis=1)
df_oct2019_grouped = df_oct2019_grouped.resample('1D').ffill()
df_oct2019_grouped.describe()
_ = show_hist(df_oct2019_grouped['Trip count'], 'Anzahl Trips', 'Anzahl (linear)', 'Trips im Oktober 2019')
df_oct2019_grouped_subscriber = df_oct2019_idx[df_oct2019_idx['User Type'] == 'Subscriber'].groupby(by=['Date'])[['Trip Duration']].agg('count').rename({'Trip Duration': 'Trip count'}, axis=1)
df_oct2019_grouped_subscriber = df_oct2019_grouped_subscriber.resample('1D').ffill()
df_oct2019_grouped_customer = df_oct2019_idx[df_oct2019_idx['User Type'] == 'Customer'].groupby(by=['Date'])[['Trip Duration']].agg('count').rename({'Trip Duration': 'Trip count'}, axis=1)
df_oct2019_grouped_customer = df_oct2019_grouped_customer.resample('1D').ffill()
fig = plt.figure(figsize=(20, 5))
ax = fig.add_subplot()
_ = df_oct2019_grouped.plot(ax=ax, y='Trip count', kind='bar', color=df_oct2019_grouped.index.map(lambda date: 'darkblue' if date.weekday() in [5, 6] else 'royalblue'))
ax.set_xlabel('Tag')
ax.set_ylabel('Anzahl Trips')
ax.set_title('Trips im Oktober 2019')
ax.yaxis.grid(linestyle='dashed')
ax.set_xticklabels(df_oct2019_grouped.index.day)
custom_lines = [Line2D([0], [0], color='royalblue', lw=4),
Line2D([0], [0], color='darkblue', lw=4)]
ax.legend(custom_lines, ['Workday', 'Weekend'])
#plt.tight_layout()
plt.show()
Es gibt am 20. Oktober und am 27. Oktober einen Einbruch der Anzahl Trips, d.h. es sind an beiden Tagen knapp über 50 Trips gemacht worden. An den anderen Tagen sind immer über 100 Trips vorhanden. Die zwei Tage, mit den wenigsten Trips, sind Sonntage. Es fällt auf, dass immer bei einem Arbeitstag die Anzhal der Trips knapp über 100 ist, an anderen Tagen aber höher. Aktuell kann dies nicht erklärt werden, da es nicht immer der gleiche Wochentag ist. Hier sollte später noch eine Untersuchung in Zusammenhang mit dem Wetter gemacht werden, vielleicht hat es an den Tagen geregnet oder es war kalt.
fig = plt.figure(figsize=(20, 5))
ax = fig.add_subplot()
ax.plot_date(df_oct2019_grouped.index, df_oct2019_grouped['Trip count'], marker='o', ls='-', label='Gesamt')
ax.plot_date(df_oct2019_grouped_subscriber.index, df_oct2019_grouped_subscriber['Trip count'], marker='o', ls='-', label='Subscriber')
ax.plot_date(df_oct2019_grouped_customer.index, df_oct2019_grouped_customer['Trip count'], marker='o', ls='-', label='Customer')
ax.set_xlabel('Tag')
ax.set_ylabel('Anzahl Trips')
ax.set_title('Trips im Oktober 2019')
ax.yaxis.grid(linestyle='dashed')
ax.xaxis.grid(linestyle='dashed')
ax.set_ylim(0, 250)
ax.legend()
#plt.tight_layout()
plt.show()
Ein paar Untersuchungen zu User Type und Gender.
df_oct2019[['User Type', 'Gender']].describe()
Anzahl der Trips, gruppiert nach User Type.
trip_count_by_user_type = df_oct2019.groupby(by=['User Type'])['Trip Duration'].agg('count')
trip_count_by_user_type.name = 'Trip Counts'
trip_count_by_user_type
Anzahl der Trips, gruppiert nach User Type und Gender.
trip_count_by_user_type_and_gender = df_oct2019.groupby(by=['User Type', 'Gender'])['Trip Duration'].agg('count')
trip_count_by_user_type_and_gender.name = 'Trip Counts'
trip_count_by_user_type_and_gender
def func(pct, allvals):
idx = np.argwhere(np.round(allvals / allvals.sum() * 100, 3).values == np.round(pct, 2))[0][0]
absolute = int(allvals[idx])
return "{:.1f}%\n({:d})".format(pct, absolute)
def func_inner(pct, allvals):
idx = np.argwhere(np.round(allvals / allvals.sum() * 100, 3).values == np.round(pct, 2))[0][0]
if idx <=2:
pct_new = allvals[idx] / allvals[0:3].sum() * 100
absolute = int(allvals[idx])
else:
pct_new = allvals[idx] / allvals[3:6].sum() * 100
absolute = int(pct_new/100.*np.sum(allvals))
return "{:.1f}% ({:d})".format(pct_new, absolute)
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(30, 15))
size = 0.3
cmap = plt.get_cmap("tab20c")
outer_colors = cmap(np.arange(3)*4)
inner_colors = cmap(np.array([1, 2, 3, 5, 6, 7]))
outer_labels = ['Customer', 'Subscriber']
inner_labels = ['unknown', 'male', 'female', 'unknown', 'male', 'female']
inner_labels_abs = ['unknown Customers', 'male Customers', 'female Customers', 'unknown Subscribers', 'male Subscribers', 'female Subscribers']
ax[0].pie(trip_count_by_user_type, autopct=lambda pct: func(pct, trip_count_by_user_type), labels=outer_labels, radius=1, colors=outer_colors,
wedgeprops=dict(width=size, edgecolor='w'), pctdistance=0.85, labeldistance=1.05)
ax[0].pie(trip_count_by_user_type_and_gender, autopct=lambda pct: func_inner(pct, trip_count_by_user_type_and_gender), labels=inner_labels, radius=1-size, colors=inner_colors,
wedgeprops=dict(width=size, edgecolor='w'), pctdistance=0.78, labeldistance=0.39, rotatelabels=True)
ax[1].pie(trip_count_by_user_type_and_gender, autopct=lambda pct: func(pct, trip_count_by_user_type_and_gender), labels=inner_labels_abs, radius=1-size, colors=inner_colors,
wedgeprops=dict(width=size, edgecolor='w'), pctdistance=0.78, labeldistance=1.05, rotatelabels=True)
ax[0].set(aspect="equal", title='Anteil `User Type` und `Gender`')
ax[1].set(aspect="equal", title='Anteil `User Type` und `Gender`')
#plt.tight_layout()
plt.show()
In der linken Grafik ist folgendes ersichtlich:
Die rechte Grafik stellt das innere Diagramm einfach nochmal dar, die Zahlen beziehen sich hier aber auf die gesamte Anzahl der Trips.
Untersuchungen zur Dauer der Trips.
df_oct2019[[col for col in df_oct2019.columns if col.startswith('Trip Duration')]].describe(percentiles=[0.05, .25, .5, .75, .95])
Folgende Zahlen sind erwähnenswert:
Das der Durchschnitt 5.3 Minuten über dem Median liegt, ist wohl den ganz langen Trips, wie z.B. der Ausreisser von 4.4 Tagen, zuzuschreiben.
_ = show_hist(df_oct2019['Trip Duration (min)'], 'min', 'Anzahl', 'Trip Duration im Oktober 2019')
Es scheint nur eine Klasse nahe bei null zu geben. Die Zahlen oben haben schon gezeigt das 95% der Werte unter 33.1 Minuten liegen. Damit die Werte rechts auch ersichtlich werden, wird das Histogramm noch mit logarithmischer y-Achse erstellt.
_ = show_hist(df_oct2019['Trip Duration (min)'], 'min', 'Anzahl', 'Trip Duration im Oktober 2019', log=True)
Ab ca. 600 Minuten gibt quasi keine Klassen mehr. Da nur 5% der Werte (d.h. genau $5000 * 0.05 = 250$ Werte) über 33.1 Minuten liegen, wird für die Übersichtlichkeit noch ein Plot mit den Trips, die kürzer als 33.1 Minuten sind, erstellt.
_ = show_hist(df_oct2019[df_oct2019['Trip Duration (min)'] < 33.1]['Trip Duration (min)'], 'min', 'Anzahl', 'Trip Duration < 33.1min im Oktober 2019 ()')
Und dazu auch noch die Tabelle mit einer Beschreibung der Werte.
df_oct2019[df_oct2019['Trip Duration (min)'] < 33.1][[col for col in df_oct2019.columns if col.startswith('Trip Duration')]].describe(percentiles=[0.05, .25, .5, .75, .95])
Die Bikes werden eher nur sehr kurze Zeit genutzt. Dies hängt vermutlich mit dem Preismodell zusammen, bei welchem 30 minuten pro Trip für Customers gratis sind. Bei einem Subscriber sind 45 Minuten gratis. Die einzelnen Gruppen werden noch in einem Boxplot miteinander verglichen.
def get_data_for_boxplot_duration(max_duration_hours = np.Inf):
df = df_oct2019[df_oct2019['Trip Duration (h)'] <= max_duration_hours]
data = [
df[df['User Type'] == 'Customer']['Trip Duration (min)'],
df[df['User Type'] == 'Subscriber']['Trip Duration (min)'],
df[df['Gender'] == '0']['Trip Duration (min)'],
df[df['Gender'] == '1']['Trip Duration (min)'],
df[df['Gender'] == '2']['Trip Duration (min)'],
df[(df['User Type'] == 'Customer') & (df['Gender'] == '0')]['Trip Duration (min)'],
df[(df['User Type'] == 'Customer') & (df['Gender'] == '1')]['Trip Duration (min)'],
df[(df['User Type'] == 'Customer') & (df['Gender'] == '2')]['Trip Duration (min)'],
df[(df['User Type'] == 'Subscriber') & (df['Gender'] == '0')]['Trip Duration (min)'],
df[(df['User Type'] == 'Subscriber') & (df['Gender'] == '1')]['Trip Duration (min)'],
df[(df['User Type'] == 'Subscriber') & (df['Gender'] == '2')]['Trip Duration (min)'],
]
labels = [
'Customers',
'Subscribers',
'Gender Unknown',
'Male',
'Women',
'Customers & Gender Unknown',
'Customers & Male',
'Customers & Woman',
'Subscribers & Gender Unknown',
'Subscribers & Male',
'Subscribers & Woman',
]
return {'data': data, 'labels': labels}
fig, ax = plt.subplots(figsize=(30, 10))
dict_data = get_data_for_boxplot_duration(1)
_ = ax.boxplot(dict_data['data'], vert=True, showmeans=True, labels=dict_data['labels'], showfliers=False)
ax.set_ylabel('min')
ax.set_title('Vergleich Trip Duration (nur Trips <= 1h)')
plt.grid(b=True)
#plt.tight_layout()
plt.show()
Folgendes fällt hier auf:
Obwohl ein Subscriber 45 Minuten pro Trip gratis fahren könnte, verwendet dieser die Bikes weniger lange als ein Customer mit nur 30 Minuten inklusive. Es wird vermutet, dass Subscriber mehrheitlich Touristen sind, welche Sightseeing betreiben und Subscriber in New York leben und das Bike nutzen, um z.B. zur Arbeit zu fahren.
Ein paar Untersuchungen zu Age 2020.
df_oct2019[['Age 2020']].describe(percentiles=[0.05, .25, .5, .75, .95])
Erkenntnisse aus der Übersicht:
print('Anzahl unterschiedlicher Werte in "Age 2020": {}'.format(len(df_oct2019['Age 2020'].unique())))
Zwar ist Age 2020 diskret, aber da es doch 67 unterschiedliche Werte hat, wird das Merkmal einfach mal als quasi-stetig behandelt und in einem Histogramm visualisiert.
_ = show_hist(df_oct2019['Age 2020'], 'Jahre', 'Anzahl', 'Age 2020 im Oktober 2019')
In der Nähe der Klasse mit 30 Jahren und der Klasse mit 50 Jahren gibt es zwei Anhäufungen. Die meisten Nutzer sind also etwa 30 oder 50 Jahre alt. Rechts scheint es noch ein paar Ausreisser zu geben, diese wären dann über 80 Jahre alt. Um diese auch noch zu sehen, hier das Histogramm nochmals mit logarithmischer y-Achse.
_ = show_hist(df_oct2019['Age 2020'], 'Jahre', 'Anzahl', 'Age 2020 im Oktober 2019', log=True)
Das Histogramm zeigt, dass die meisten unter 80 Jahre alt sind, darüber gibt es nur wenige Klassen. Die beiden Klassen ab 120 Jahre sind sehr unrealistisch und hier wurden vermutlich falsche Angaben gemacht.
fig = plt.figure(figsize=(15, 2))
ax = plt.subplot()
_ = df_oct2019.boxplot(column=['Age 2020'], vert=False, showmeans=True, labels=['Age 2020'], widths=0.7)
ax.set_xlabel('Jahre')
ax.set_title('Alter im Jahr 2020')
#plt.tight_layout()
plt.show()
Im Boxplot ist ersichtlich,
def get_data_for_boxplot_age2020(max_age = np.Inf):
df = df_oct2019[df_oct2019['Age 2020'] <= max_age]
data = [
df[df['User Type'] == 'Customer']['Age 2020'],
df[df['User Type'] == 'Subscriber']['Age 2020'],
df[df['Gender'] == '0']['Age 2020'],
df[df['Gender'] == '1']['Age 2020'],
df[df['Gender'] == '2']['Age 2020'],
df[(df['User Type'] == 'Customer') & (df['Gender'] == '0')]['Age 2020'],
df[(df['User Type'] == 'Customer') & (df['Gender'] == '1')]['Age 2020'],
df[(df['User Type'] == 'Customer') & (df['Gender'] == '2')]['Age 2020'],
df[(df['User Type'] == 'Subscriber') & (df['Gender'] == '0')]['Age 2020'],
df[(df['User Type'] == 'Subscriber') & (df['Gender'] == '1')]['Age 2020'],
df[(df['User Type'] == 'Subscriber') & (df['Gender'] == '2')]['Age 2020'],
]
labels = [
'Customers',
'Subscribers',
'Gender Unknown',
'Male',
'Women',
'Customers & Gender Unknown',
'Customers & Male',
'Customers & Woman',
'Subscribers & Gender Unknown',
'Subscribers & Male',
'Subscribers & Woman',
]
return {'data': data, 'labels': labels}
fig, ax = plt.subplots(figsize=(30, 10))
dict_data = get_data_for_boxplot_age2020(80)
_ = ax.boxplot(dict_data['data'], vert=True, showmeans=True, labels=dict_data['labels'], showfliers=True)
ax.set_ylabel('Jahre')
ax.set_title('Vergleich Age 2020 (nur Alter <= 80 Jahre)')
plt.grid(b=True)
#plt.tight_layout()
plt.show()
Im Vergleich ist ersichtlich:
Untersuchung der Anzahl Trips nach Alter.
trip_count_customers_groupby_age = df_oct2019[df_oct2019['User Type'] == 'Customer'].groupby(by=['Age 2020', 'Gender'])['Trip Duration'].agg('count').reset_index().rename({'Trip Duration':'Trip count'}, axis=1)
trip_count_customers_groupby_age = trip_count_customers_groupby_age.pivot(index='Age 2020', columns='Gender', values='Trip count')
trip_count_customers_groupby_age.columns = ['unknown', 'male', 'female']
#trip_count_customers_groupby_age.head()
trip_count_subscribers_groupby_age = df_oct2019[df_oct2019['User Type'] == 'Subscriber'].groupby(by=['Age 2020', 'Gender'])['Trip Duration'].agg('count').reset_index().rename({'Trip Duration':'Trip count'}, axis=1)
trip_count_subscribers_groupby_age = trip_count_subscribers_groupby_age.pivot(index='Age 2020', columns='Gender', values='Trip count')
trip_count_subscribers_groupby_age.columns = ['unknown', 'male', 'female']
#trip_count_subscribers_groupby_age.head()
fig, ax = plt.subplots(nrows=2, ncols=1, figsize=(20, 8), sharex=True, sharey=False)
trip_count_customers_groupby_age.plot(ax=ax[0], kind='bar', stacked=True, width=0.9)
ax[0].set_axisbelow(False)
ax[0].yaxis.grid(linestyle='dashed')
ax[0].xaxis.grid(linestyle='dashed')
ax[0].set_ylabel('Trip Count')
ax[0].set_title('Customers - Anzahl Trips nach Alter')
trip_count_subscribers_groupby_age.plot(ax=ax[1], kind='bar', stacked=True, width=0.9)
ax[1].set_axisbelow(False)
ax[1].yaxis.grid(linestyle='dashed')
ax[1].xaxis.grid(linestyle='dashed')
ax[1].set_ylabel('Trip Count')
ax[1].set_title('Subscribers - Anzahl Trips nach Alter')
plt.tight_layout()
plt.show()
In den beiden obenstehenden Grafiken ist folgendes ersichtlich:
Ein paar Untersuchungen zu den Stationen.
df_oct2019[[col for col in df_oct2019.columns if col.endswith('Station Latitude') or col.endswith('Station Longitude') or col.endswith('Station Name') or col == 'Linear Distance']].describe(include='all')
Erkenntnisse aus der Übersicht:
Untersuchung der Distanz der Trips nach Alter.
trip_distance_customers_groupby_age = df_oct2019[df_oct2019['User Type'] == 'Customer'].groupby(by=['Age 2020', 'Gender'])['Linear Distance'].agg('sum').reset_index().rename({'Linear Distance':'Linear Distance sum'}, axis=1)
trip_distance_customers_groupby_age = trip_distance_customers_groupby_age.pivot(index='Age 2020', columns='Gender', values='Linear Distance sum')
trip_distance_customers_groupby_age.columns = ['unknown', 'male', 'female']
trip_distance_customers_groupby_age.head()
trip_distance_subscribers_groupby_age = df_oct2019[df_oct2019['User Type'] == 'Subscriber'].groupby(by=['Age 2020', 'Gender'])['Linear Distance'].agg('sum').reset_index().rename({'Linear Distance':'Linear Distance sum'}, axis=1)
trip_distance_subscribers_groupby_age = trip_distance_subscribers_groupby_age.pivot(index='Age 2020', columns='Gender', values='Linear Distance sum')
trip_distance_subscribers_groupby_age.columns = ['unknown', 'male', 'female']
trip_distance_subscribers_groupby_age.head()
fig, ax = plt.subplots(nrows=2, ncols=1, figsize=(20, 8), sharex=True, sharey=False)
trip_distance_customers_groupby_age.plot(ax=ax[0], kind='bar', stacked=True, width=0.9)
ax[0].set_axisbelow(False)
ax[0].yaxis.grid(linestyle='dashed')
ax[0].xaxis.grid(linestyle='dashed')
ax[0].set_ylabel('Linear Distance (km)')
ax[0].set_title('Customers - Anzahl Kilometer nach Alter')
trip_distance_subscribers_groupby_age.plot(ax=ax[1], kind='bar', stacked=True, width=0.9)
ax[1].set_axisbelow(False)
ax[1].yaxis.grid(linestyle='dashed')
ax[1].xaxis.grid(linestyle='dashed')
ax[1].set_ylabel('Linear Distance (km)')
ax[1].set_title('Subscribers - Anzahl Kilometer nach Alter')
plt.tight_layout()
plt.show()
In den beiden obenstehenden Grafiken ist folgendes ersichtlich:
starts_count = df_oct2019.groupby(by=['Start Station Name'])['Trip Duration'].agg('count').reset_index().rename({'Trip Duration' :'Starts count', 'Start Station Name': 'Station Name'}, axis=1).set_index('Station Name')
#starts_count.head()
ends_count = df_oct2019.groupby(by=['End Station Name'])['Trip Duration'].agg('count').reset_index().rename({'Trip Duration' :'Ends count', 'End Station Name': 'Station Name'}, axis=1).set_index('Station Name')
#ends_count.head()
start_stations = df_oct2019[[col for col in df_oct2019.columns if col.startswith('Start Station')]].rename({'Start Station ID': 'Station ID', 'Start Station Name': 'Station Name', 'Start Station Latitude': 'Latitude', 'Start Station Longitude': 'Longitude'}, axis=1).set_index('Station Name')
start_stations = start_stations[start_stations.duplicated() == False]
#start_stations
end_stations = df_oct2019[[col for col in df_oct2019.columns if col.startswith('End Station')]].rename({'End Station ID': 'Station ID', 'End Station Name': 'Station Name', 'End Station Latitude': 'Latitude', 'End Station Longitude': 'Longitude'}, axis=1).set_index('Station Name')
end_stations = end_stations[end_stations.duplicated() == False]
#end_stations
stations = pd.concat([start_stations, end_stations])
stations = stations[stations.duplicated() == False]
#stations
trip_counts_by_station = pd.concat([starts_count, ends_count, stations], axis=1)
trip_counts_by_station.insert(2, 'Trip count', trip_counts_by_station[['Starts count', 'Ends count']].sum(axis=1))
trip_counts_by_station = trip_counts_by_station.sort_values(by='Trip count', ascending=False)
trip_counts_by_station.index.name = 'Station Name'
#trip_counts_by_station.head()
fig, ax = plt.subplots(figsize=(20, 8))
trip_counts_by_station[0:50][['Starts count', 'Ends count']].plot(ax=ax, kind='bar', stacked=True, width=0.9)
ax.set_axisbelow(False)
ax.yaxis.grid(linestyle='dashed')
ax.xaxis.grid(linestyle='dashed')
ax.set_ylabel('Trip count')
ax.set_title('Top 50 Stationen')
plt.tight_layout()
plt.show()
Die 50 Stationen mit den meisten Trips wurden in einem Barplot dargestellt.
Die Top 50 Stationen werden noch auf einer Map dargestellt. Dabei stellt die Grösse und die Farbe des Kreises die Anzahl Trips dar. Gelb ist die Station 'Pershing Square North' mit den meisten Trips. Diese liegt am Grand Central Terminal, was vermutlich auch der Grund für die stärkere Auslastung ist.
import tilemapbase
tilemapbase.init(create=True)
tiles_osm = tilemapbase.tiles.Carto_Light
#center_nyc = (-73.967419961490, 40.73100351879932)
center_nyc = (-73.984803, 40.745687)
degree_range_heigth = 0.07
degree_range_width = 0.06
extent = tilemapbase.Extent.from_lonlat(
center_nyc[0] - degree_range_heigth, center_nyc[0] + degree_range_heigth,
center_nyc[1] - degree_range_width, center_nyc[1] + degree_range_width)
extent = extent.to_aspect(1.0)
projection = trip_counts_by_station[0:50].apply(lambda row: tilemapbase.project(latitude=row['Latitude'], longitude=row['Longitude']), axis=1)
x_coords = []
y_coords =[]
names = []
counts = []
for idx, val in enumerate(projection.values):
x_coords.append(val[0])
y_coords.append(val[1])
names.append(trip_counts_by_station[0:50].reset_index()['Station Name'][idx])
counts.append(trip_counts_by_station[0:50]['Trip count'][idx])
fig, ax = plt.subplots(figsize=(10, 10), dpi=150)
ax.xaxis.set_visible(False)
ax.yaxis.set_visible(False)
plotter = tilemapbase.Plotter(extent, tiles_osm, width=600)
plotter.plot(ax, tiles_osm)
sc = ax.scatter(x_coords, y_coords, c=counts, s=counts, cmap=plt.cm.get_cmap('viridis'), alpha=0.9, linewidths=3)
cbar = plt.colorbar(sc, fraction=0.046, pad=0.02, label='Anzahl Trips', orientation='vertical')
ax.set_title('Top 50 Stationen')
#plt.tight_layout()
plt.show()
Auf der Karte ist die Station 'Pershing Square North' mit einem gelben Punkt markiert, diese Station hat auch die meisten Trips.
Untersuchungen zur Temperatur.
df_oct2019['temp'].describe()
_ = show_hist(df_oct2019['temp'], '°C', 'Anzahl', 'Temperatur im Oktober 2019')
Wie oben bei der Übersicht mit den Zahlen ersichtlich, häufen sich im Histogramm die Werte zwischen 14 und 18°C.
fig = plt.figure(figsize=(15, 2))
ax = plt.subplot()
_ = df_oct2019.boxplot(column=['temp'], vert=False, showmeans=True, labels=['temp'], widths=0.7)
ax.set_xlabel('°C')
ax.set_title('Temperatur im Oktober 2019')
#plt.tight_layout()
plt.show()
Damit eine Aussage darüber gemacht werden kann, wie die Temperatur an welchen Tagen war, wird ein Plot über den Monat Oktober 2019 dargestellt.
fig = plt.figure(figsize=(20, 5))
ax = fig.add_subplot()
_ = df_oct2019.sort_values('Start Time')[['Start Time', 'temp']].set_index('Start Time').plot(ax=ax, y='temp')
ax.set_xlabel('Start Time')
ax.set_ylabel('°C')
ax.set_title('Temperatur im Oktober 2019')
ax.yaxis.grid(linestyle='dashed')
ax.xaxis.grid(linestyle='dashed')
ax.xaxis.set_major_locator(mdates.DayLocator(interval=1))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%d"))
ax.set_ylim(0, 35)
ax.set_xlim([datetime(2019, 10, 1, 0, 0, 0, tzinfo=tz.gettz('US/Eastern')), datetime(2019, 11, 1, 0, 0, 0, tzinfo=tz.gettz('US/Eastern'))])
#plt.tight_layout()
plt.show()
Diese beiden Tage werden in den nächsten beiden Kapiteln noch in einem eigenen Tagesverlauf dargestellt.
df_oct2019[['Start Time', 'temp']].sort_values('temp', ascending=False).head(1)
Der Tag mit der höchsten Temperatur ist der 2.10.2019.
fig = plt.figure(figsize=(20, 5))
ax = fig.add_subplot()
_ = df_oct2019[['Start Time', 'temp']].set_index('Start Time')['2019-10-02'].plot(ax=ax, y='temp')
ax.set_xlabel('Start Time')
ax.set_ylabel('°C')
ax.set_title('Temperatur am 2.10.2019')
ax.yaxis.grid(linestyle='dashed')
ax.xaxis.grid(linestyle='dashed')
ax.xaxis.set_major_locator(mdates.HourLocator(interval=1))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M"))
ax.set_ylim(0, 35)
ax.set_xlim([datetime(2019, 10, 2, 0, 0, 0, tzinfo=tz.gettz('US/Eastern')), datetime(2019, 10, 3, 0, 0, 0, tzinfo=tz.gettz('US/Eastern'))])
#plt.tight_layout()
plt.show()
Die Höchsttemperatur von 33.1°C wurde um 15 Uhr erreicht. Die anderen Trips wurden immer mit Temperaturen über 22 Grad gefahren, erst Abends ab ca. 22:30 fiel die Temperatur unter 22 °C.
df_oct2019[['Start Time', 'temp']].sort_values('temp', ascending=True).head(1)
Der Tag mit der tiefsten Temperatur ist der 19.10.2019.
fig = plt.figure(figsize=(20, 5))
ax = fig.add_subplot()
_ = df_oct2019[['Start Time', 'temp']].set_index('Start Time')['2019-10-19'].plot(ax=ax, y='temp')
ax.set_xlabel('Start Time')
ax.set_ylabel('°C')
ax.set_title('Temperatur am 19.10.2019')
ax.yaxis.grid(linestyle='dashed')
ax.xaxis.grid(linestyle='dashed')
ax.xaxis.set_major_locator(mdates.HourLocator(interval=1))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M"))
ax.set_ylim(0, 20)
ax.set_xlim([datetime(2019, 10, 19, 0, 0, 0, tzinfo=tz.gettz('US/Eastern')), datetime(2019, 10, 20, 0, 0, 0, tzinfo=tz.gettz('US/Eastern'))])
#plt.tight_layout()
plt.show()
Die Tiefsttemperatur von 6.1°C wurde um 7 Uhr erreicht. Danach wurde es wieder etwas wärmer.
Untersuchungen zum Niederschlag.
df_oct2019['rain_1h'].describe()
_ = show_hist(df_oct2019['rain_1h'], 'mm', 'Anzahl', 'Niederschlag im Oktober 2019')
Im Histogramm ist nur ersichtlich das die Werte sich in der Nähe von 0 häufen, daher noch ein Histogramm mit logarithmischer y-Achse.
_ = show_hist(df_oct2019['rain_1h'], 'mm', 'Anzahl', 'Niederschlag im Oktober 2019', log=True)
Aus dem Histogramm lässt sich nur herauslesen, dass es meistens nur bis zu 2mm geregnet hat und manchmal auch mehr Niederschlag gab. Aber an welchen Tagen es im Oktober 2019 geregnet hat, lässt sich nicht erkennen. Auf einen Boxplot wird verzichtet, denn dieser wird das gleiche Bild wie das obige Histogramm zeigen und wird keine neuen Erkenntnisse liefern.
fig = plt.figure(figsize=(20, 5))
ax = fig.add_subplot()
_ = df_oct2019.sort_values('Start Time')[['Start Time', 'rain_1h']].plot(ax=ax, x='Start Time', y='rain_1h')
ax.set_xlabel('Start Time')
ax.set_ylabel('mm')
ax.set_title('Niederschlag im Oktober 2019')
ax.yaxis.grid(linestyle='dashed')
ax.xaxis.grid(linestyle='dashed')
ax.xaxis.set_major_locator(mdates.DayLocator(interval=1))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%d"))
ax.set_xlim([datetime(2019, 10, 1, 0, 0, 0, tzinfo=tz.gettz('US/Eastern')), datetime(2019, 11, 1, 0, 0, 0, tzinfo=tz.gettz('US/Eastern'))])
#plt.tight_layout()
plt.show()
##### NEXT ANALYSIS ###
summary-daily-subscribers_only-citibike-tripweather.parquet¶df_sumsubs = pd.read_parquet(os.path.join(path, 'summary-daily-subscribers_only-citibike-tripweather.parquet'))
df_sumsubs.tail().T
| Statistische Einheit | Merkmal | Skalenniveau | Kontinuität | Beschreibung |
|---|---|---|---|---|
| Day | Date | Intervall | diskret | Datum |
| Trip | Trip count | Verhältnis | diskret | Gesamte Anzahl Trips an diesem Tag |
| Trip | Trip Duration mean | Verhältnis | stetig | Durchschnittliche Dauer |
| Trip | Trip Duration std | Verhältnis | stetig | Standardabweichung der Dauer |
| Trip | Trip Duration min | Verhältnis | stetig | Dauer des schnellsten Trips |
| Trip | Trip Duration median | Verhältnis | stetig | Median der Dauer |
| Trip | Trip Duration max | Verhältnis | stetig | Dauer des längsten Trips |
| Trip | Linear Distance mean | Verhältnis | stetig | Durchschnittliche Distanz (Luftlinie zwischen Stationen) |
| Trip | Linear Distance std | Verhältnis | stetig | Standardabweichung der Distanz |
| Trip | Linear Distance min | Verhältnis | stetig | Distanz des kürzesten Trips |
| Trip | Linear Distance median | Verhältnis | stetig | Median der Distanz |
| Trip | Linear Distance max | Verhältnis | stetig | Distanz des weitesten Trips |
| User | Age 2020 count | Verhältnis | diskret | Gesamte Anzahl angegebener Alter |
| User | Age 2020 mean | Verhältnis | stetig | Durchschnittliches Alter |
| User | Age 2020 std | Verhältnis | diskret | Standardabweichung des Alters |
| User | Age 2020 min | Verhältnis | diskret | Jüngstes Alter |
| User | Age 2020 median | Verhältnis | stetig | Median des Alters |
| User | Age 2020 max | Verhältnis | diskret | Ältestes Alter |
| Temperature | temp mean | Intervall | stetig | Durchschnittliche Temperatur (°C) |
| Temperature | temp std | Intervall | stetig | Standardabweichung der Temperatur (°C) |
| Temperature | temp median | Intervall | stetig | Median der Temperatur (°C) |
| Temperature | temp min | Intervall | stetig | Tiefste Temperatur (°C) |
| Temperature | temp max | Intervall | stetig | Höchste Temperatur (°C) |
| Wind | wind_speed mean | Verhältnis | stetig | Durchschnittliche Windgeschwindigkeit (m/s) |
| Wind | wind_speed std | Verhältnis | stetig | Standardabweichung der Windgeschwindigkeit (m/s) |
| Wind | wind_speed median | Verhältnis | stetig | Median der Windgeschwindigkeit (m/s) |
| Wind | wind_speed min | Verhältnis | stetig | Tiefste Windgeschwindigkeit (m/s) |
| Wind | wind_speed max | Verhältnis | stetig | Höchste Windgeschwindigkeit (m/s) |
| Rainfall | rain_1h sum | Verhältnis | stetig | Gesamte Menge an Niederschlag (mm) |
| Rainfall | rain_1h min | Verhältnis | stetig | Tiefste Menge an Niederschlag in 1h am Tag (mm) |
| Rainfall | rain_1h max | Verhältnis | stetig | Höchste Menge an Niederschlag in 1h am Tag (mm) |
| Snowfall | snow_1h sum | Verhältnis | stetig | Gesamte Menge an Schneefall (mm) |
| Snowfall | snow_1h min | Verhältnis | stetig | Tiefste Menge an Schneefall in 1h am Tag (mm) |
| Snowfall | snow_1h max | Verhältnis | stetig | Höchste Menge an Schneefall in 1h am Tag (mm) |
summary-daily-subscribers_only-gender_grouped-citibike-tripweather.parquet¶df_sumsubs_bygen = pd.read_parquet(os.path.join(path, 'summary-daily-subscribers_only-gender_grouped-citibike-tripweather.parquet'))
df_sumsubs_bygen.tail().T
Zusätzlich zu den Merkmalen aus summary-daily-subscribers_only-gender_grouped-citibike-tripweather.parquet, gibt es noch zusätzlich das folgende Merkmal:
| Statistische Einheit | Merkmal | Skalenniveau | Beschreibung |
|---|---|---|---|
| User | Gender | Nominal | Geschlecht des Benutzers: 0=unknown; 1=male; 2=female |
import os
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
%matplotlib inline
pd.options.display.float_format = '{:.5f}'.format
path = './../data/citibike/tripdata'
tripweather = pd.read_parquet(os.path.join(path, 'samples_5000_201910-citibike-tripweather-data.parquet'))
tripweather.head()
tripweather.info()
tripweather.shape
tripweather.isna().sum().sum()
def filter_numeric_columns(df):
res = []
for col in df.columns:
if df[col].dtype == np.float64 or df[col].dtype == np.int64 or isinstance(df[col].dtype, pd.Int64Dtype):
res.append(col)
return res
%%time
numeric_columns = filter_numeric_columns(tripweather)
fig = plt.figure(figsize=(20, len(numeric_columns) * 2))
gs = gridspec.GridSpec(len(numeric_columns), 2, figure=fig)
for idx, col in enumerate(numeric_columns):
ax = fig.add_subplot(gs[idx, 0])
ax.set_title(col)
hist = tripweather[col].hist(ax=ax, bins=25)
ax = fig.add_subplot(gs[idx, 1])
ax.set_title(col + ' (log scale)')
hist = tripweather[col].hist(ax=ax, bins=25)
ax.set_yscale('log')
fig.tight_layout()
plt.show()
tripweather_samples_per_day = pd.read_parquet(os.path.join(path, 'samples_per_day-citibike-tripweather.parquet'))
tripweather_samples_per_day.shape
tripweather_samples_per_day.isna().sum()
def filter_numeric_columns(df):
res = []
for col in df.columns:
if df[col].dtype == np.float64 or df[col].dtype == np.int64 or isinstance(df[col].dtype, pd.Int64Dtype):
res.append(col)
return res
%%time
numeric_columns = filter_numeric_columns(tripweather_samples_per_day)
fig = plt.figure(figsize=(20, len(numeric_columns) * 2))
gs = gridspec.GridSpec(len(numeric_columns), 2, figure=fig)
for idx, col in enumerate(numeric_columns):
ax = fig.add_subplot(gs[idx, 0])
ax.set_title(col)
hist = tripweather_samples_per_day[col].hist(ax=ax, bins=25)
ax = fig.add_subplot(gs[idx, 1])
ax.set_title(col + ' (log scale)')
hist = tripweather_samples_per_day[col].hist(ax=ax, bins=25)
ax.set_yscale('log')
fig.tight_layout()
plt.show()
tripweather_samples_per_month = pd.read_parquet(os.path.join(path, 'samples_per_month-citibike-tripweather.parquet'))
tripweather_samples_per_month.shape
tripweather_samples_per_month.isna().sum()
def filter_numeric_columns(df):
res = []
for col in df.columns:
if df[col].dtype == np.float64 or df[col].dtype == np.int64 or isinstance(df[col].dtype, pd.Int64Dtype):
res.append(col)
return res
%%time
numeric_columns = filter_numeric_columns(tripweather_samples_per_month)
fig = plt.figure(figsize=(20, len(numeric_columns) * 2))
gs = gridspec.GridSpec(len(numeric_columns), 2, figure=fig)
for idx, col in enumerate(numeric_columns):
ax = fig.add_subplot(gs[idx, 0])
ax.set_title(col)
hist = tripweather_samples_per_month[col].hist(ax=ax, bins=25)
ax = fig.add_subplot(gs[idx, 1])
ax.set_title(col + ' (log scale)')
hist = tripweather_samples_per_month[col].hist(ax=ax, bins=25)
ax.set_yscale('log')
fig.tight_layout()
plt.show()
tripweather_summary = pd.read_parquet(os.path.join(path, 'summary-daily-subscribers_only-citibike-tripweather.parquet'))
tripweather_summary.head()
tripweather_summary.info()
tripweather_summary.shape
tripweather_summary.isna().sum().sum()
def filter_numeric_columns(df):
res = []
for col in df.columns:
if df[col].dtype == np.float64 or df[col].dtype == np.int64 or isinstance(df[col].dtype, pd.Int64Dtype):
res.append(col)
return res
%%time
numeric_columns = filter_numeric_columns(tripweather_summary)
fig = plt.figure(figsize=(20, len(numeric_columns) * 2))
gs = gridspec.GridSpec(len(numeric_columns), 2, figure=fig)
for idx, col in enumerate(numeric_columns):
ax = fig.add_subplot(gs[idx, 0])
ax.set_title(col)
hist = tripweather_summary[col].hist(ax=ax, bins=25)
ax = fig.add_subplot(gs[idx, 1])
ax.set_title(col + ' (log scale)')
hist = tripweather_summary[col].hist(ax=ax, bins=25)
ax.set_yscale('log')
fig.tight_layout()
plt.show()
tripweather_summary_grouped = pd.read_parquet(os.path.join(path, 'summary-daily-subscribers_only-gender_grouped-citibike-tripweather.parquet'))
tripweather_summary_grouped.head()
tripweather_summary_grouped.info()
tripweather_summary_grouped.shape
tripweather_summary_grouped.isna().sum().sum()
def filter_numeric_columns(df):
res = []
for col in df.columns:
if df[col].dtype == np.float64 or df[col].dtype == np.int64 or isinstance(df[col].dtype, pd.Int64Dtype):
res.append(col)
return res
%%time
numeric_columns = filter_numeric_columns(tripweather_summary_grouped)
fig = plt.figure(figsize=(20, len(numeric_columns) * 2))
gs = gridspec.GridSpec(len(numeric_columns), 2, figure=fig)
for idx, col in enumerate(numeric_columns):
ax = fig.add_subplot(gs[idx, 0])
ax.set_title(col)
hist = tripweather_summary_grouped[col].hist(ax=ax, bins=25)
ax = fig.add_subplot(gs[idx, 1])
ax.set_title(col + ' (log scale)')
hist = tripweather_summary_grouped[col].hist(ax=ax, bins=25)
ax.set_yscale('log')
fig.tight_layout()
plt.show()